package xyz.tanxiao.links.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import xyz.tanxiao.links.common.entity.PasswordAccessRequest;
import xyz.tanxiao.links.common.enums.LinksCacheKeyEnum;
import xyz.tanxiao.links.common.exception.GeneralAccessException;
import xyz.tanxiao.links.common.exception.PasswordAccessException;
import xyz.tanxiao.links.common.listener.ShortLinkAccessEvent;
import xyz.tanxiao.links.common.utils.LinkMappingValidator;
import xyz.tanxiao.links.common.utils.NetworkUtils;
import xyz.tanxiao.links.config.GlobalProperties;
import xyz.tanxiao.links.core.cache.LayeringCacheOperations;
import xyz.tanxiao.links.entity.SlLinkAccess;
import xyz.tanxiao.links.entity.SlLinkMapping;
import xyz.tanxiao.links.mapper.SlLinkAccessMapper;
import xyz.tanxiao.links.service.SlLinkAccessService;
import xyz.tanxiao.links.service.SlLinkMappingService;

import java.util.Objects;

@Slf4j
@RequiredArgsConstructor
@Service
public class SlLinkAccessServiceImpl extends ServiceImpl<SlLinkAccessMapper, SlLinkAccess> implements SlLinkAccessService {

	private final ApplicationEventPublisher eventPublisher;

	private final GlobalProperties globalProperties;

	private final LayeringCacheOperations layeringCacheOperations;

	private final SlLinkMappingService linkMappingService;


	/**
	 * 从缓存或数据库中获取链接映射信息
	 *
	 * @param mappingKey 映射Key
	 * @return 链接映射信息
	 */
	@Override
	public SlLinkMapping getCacheLinkMapping(String mappingKey) {
		String cacheKey = LinksCacheKeyEnum.SHORT_LINKS_CACHE_KEY.getCacheKey() + mappingKey;
		return this.layeringCacheOperations.getSet(cacheKey, () ->
			this.linkMappingService.getOne(
				Wrappers.lambdaQuery(SlLinkMapping.class).eq(SlLinkMapping::getMappingKey, mappingKey
			)
		));
	}


	/**
	 * 通过映射Key获取长链接映射信息
	 *
	 * @param mappingKey 映射Key
	 * @return 长链接映射信息
	 */
	@Override
	public SlLinkMapping generalAccess(String mappingKey) {
		String domainName = this.globalProperties.getDomainName();
		String shortLink = domainName + mappingKey;

		SlLinkMapping linkMapping = this.getCacheLinkMapping(mappingKey);
		if (Objects.isNull(linkMapping)) {
			throw new GeneralAccessException(shortLink, "访问失败，无效的短链接。");
		}

		// 验证短链接的映射信息
		LinkMappingValidator validator = LinkMappingValidator.setVerifyMapping(linkMapping)
			.verifyMappingStatus()
			.verifyEnableTime()
			.verifyDisableTime()
			.verifyAllowTotal(this);

		// 检查短链接是否设置了访问密码，如果没有设置则添加访问记录，反之重定向到密码输入页面输入密码。
		if (Objects.isNull(linkMapping.getAccessPassword())) {
			linkMapping.setRedirectUrl(linkMapping.getLongLink());
			ShortLinkAccessEvent accessEvent = new ShortLinkAccessEvent();
			accessEvent.setMappingKey(mappingKey);
			accessEvent.setResult(validator.isVerifyResult() ? 1 : 0);
			accessEvent.setResultInfo(validator.getVerifyMessage());
			this.eventPublisher.publishEvent(accessEvent);
		} else {
			// 处理中文字符域名引起SpringMVC重定向的问题
			String chineseDomainName = NetworkUtils.chineseDomainNameToASCII(domainName);
			linkMapping.setRedirectUrl(String.format("%spassword?mappingKey=%s", chineseDomainName, mappingKey));
		}

		// 验证成功，返回链接映射信息。
		if (validator.isVerifyResult()) {
			return linkMapping;
		} else {
			throw new GeneralAccessException(shortLink, validator.getVerifyMessage());
		}
	}


	/**
	 * 密码访问短链接
	 *
	 * @param request 请求参数
	 * @return 链接映射信息
	 */
	@Override
	public SlLinkMapping passwordAccess(PasswordAccessRequest request) {
		String mappingKey = request.getMappingKey();
		String domainName = this.globalProperties.getDomainName();
		String shortLink = domainName + mappingKey;

		SlLinkMapping linkMapping = this.getCacheLinkMapping(mappingKey);
		if (Objects.isNull(linkMapping)) {
			throw new PasswordAccessException(shortLink, "访问失败，无效的短链接。");
		}

		// 验证短链接的映射信息
		LinkMappingValidator validator = LinkMappingValidator.setVerifyMapping(linkMapping)
			.verifyMappingStatus()
			.verifyEnableTime()
			.verifyDisableTime()
			.verifyAllowTotal(this)
			.verifyAccessPassword(request.getAccessPassword());

		// 封装请求记录参数，添加访问记录。
		ShortLinkAccessEvent accessEvent = new ShortLinkAccessEvent();
		accessEvent.setMappingKey(linkMapping.getMappingKey());
		accessEvent.setResult(validator.isVerifyResult() ? 1 : 0);
		accessEvent.setResultInfo(validator.getVerifyMessage());
		this.eventPublisher.publishEvent(accessEvent);

		// 验证失败，抛出异常信息。
		if (validator.isVerifyResult()) {
			return linkMapping;
		} else {
			throw new PasswordAccessException(shortLink, validator.getVerifyMessage());
		}
	}

}
